﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;

namespace Oops.Xna.Framework
{
	public class CompositeGameComponent<T> : DrawableGameComponent where T : IGameComponent
	{
		#region DrawOrderComparer Declaration

		internal class DrawOrderComparer : IComparer<IDrawable>
		{
			public static readonly DrawOrderComparer Default = new DrawOrderComparer();

			public int Compare(IDrawable x, IDrawable y)
			{
				if (x == null && y == null)
				{
					return 0;
				}

				if (x != null)
				{
					if (y == null)
					{
						return -1;
					}

					if (x.Equals(y))
					{
						return 0;
					}

					if (x.DrawOrder < y.DrawOrder)
					{
						return -1;
					}
				}

				return 1;
			}
		}

		#endregion

		#region UpdateOrderComparer Declaration

		internal class UpdateOrderComparer : IComparer<IUpdateable>
		{
			public static readonly UpdateOrderComparer Default = new UpdateOrderComparer();

			public int Compare(IUpdateable x, IUpdateable y)
			{
				if (x == null && y == null)
				{
					return 0;
				}

				if (x != null)
				{
					if (y == null)
					{
						return -1;
					}

					if (x.Equals(y))
					{
						return 0;
					}

					if (x.UpdateOrder < y.UpdateOrder)
					{
						return -1;
					}
				}

				return 1;
			}
		}

		#endregion

		private GameComponentCollection<T> components;

		private bool initialized;
		private List<IGameComponent> notYetInitialized = new List<IGameComponent>();
		private List<IUpdateable> updateables = new List<IUpdateable>();
		private List<IDrawable> drawables = new List<IDrawable>();

		/// <summary>
		/// Initialize a new instance of the <see cref="CompositeGameComponent"/> class.
		/// </summary>
		/// <param name="game">The associated <see cref="Microsoft.Xna.Framework.Game"/>.</param>
		public CompositeGameComponent(Game game) : base(game)
		{
			// Create the component collection and subscribe to some of it's events.
			this.components = new GameComponentCollection<T>();
			this.components.ComponentAdded += new EventHandler<GameComponentCollectionEventArgs<T>>(this.OnComponentAdded);
			this.components.ComponentRemoved += new EventHandler<GameComponentCollectionEventArgs<T>>(this.OnComponentRemoved);
		}

		/// <summary>
		/// Gets the collection of registered <typeparam name="T" /> instances.
		/// </summary>
		public GameComponentCollection<T> Components
		{
			get { return this.components; }
		}

		#region GameComponent

		protected override void Dispose(bool disposing)
		{
			if (disposing)
			{
				lock (this)
				{
					// Dispose of all game components.
					for (int i = this.components.Count - 1; i >= 0; i--)
					{
						IDisposable disposable = this.components[i] as IDisposable;
						if (disposable != null)
						{
							disposable.Dispose();
						}
					}
				}
			}

			// Call inherited method.
			base.Dispose(disposing);
		}

		#endregion

		#region IGameComponent Members

		public override void Initialize()
		{
			// Initialize all necessary particle systems.
			while (this.notYetInitialized.Count != 0)
			{
				this.notYetInitialized[0].Initialize();
				this.notYetInitialized.RemoveAt(0);
			}

			// Call inherited method.
			base.Initialize();

			// Set the initialized flag.
			this.initialized = true;
		}

		#endregion

		#region IUpdateable Members

		public override void Update(GameTime gameTime)
		{
			// Update all enabled particle systems.
			for (int i = 0; i < this.updateables.Count; i++)
			{
				IUpdateable updateable = this.updateables[i];
				if (updateable.Enabled)
				{
					updateable.Update(gameTime);
				}
			}

			// Call inherited method.
			base.Update(gameTime);
		}

		#endregion

		#region IDrawable Members

		public override void Draw(GameTime gameTime)
		{
			// Draw all visible particle systems.
			for (int i = 0; i < this.drawables.Count; i++)
			{
				IDrawable drawable = this.drawables[i];
				if (drawable.Visible)
				{
					drawable.Draw(gameTime);
				}
			}

			// Call inherited method.
			base.Draw(gameTime);
		}

		#endregion

		protected virtual void OnComponentAdded(object sender, GameComponentCollectionEventArgs<T> e)
		{
			// Initialize or add the item to the initialization pending list.
			if (this.initialized)
			{
				e.Item.Initialize();
			}
			else
			{
				this.notYetInitialized.Add(e.Item);
			}

			// Determine if the item is updateable.
			IUpdateable updateable = e.Item as IUpdateable;
			if (updateable != null)
			{
				// Determine where the particle system should be inserted.
				int index = this.updateables.BinarySearch(updateable, UpdateOrderComparer.Default);
				if (index < 0)
				{
					index = ~index;
					while ((index < this.updateables.Count) && (this.updateables[index].UpdateOrder == updateable.UpdateOrder))
					{
						index++;
					}
					this.updateables.Insert(index, updateable);

					// Subscribe to some of the item's events.
					updateable.UpdateOrderChanged += new EventHandler(this.OnComponentUpdateOrderChanged);
				}
			}

			// Determine if the item is drawable.
			IDrawable drawable = e.Item as IDrawable;
			if (drawable != null)
			{
				// Determine where the item should be inserted.
				int index = this.drawables.BinarySearch(drawable, DrawOrderComparer.Default);
				if (index < 0)
				{
					index = ~index;
					while ((index < this.drawables.Count) && (this.drawables[index].DrawOrder == drawable.DrawOrder))
					{
						index++;
					}

					// Subscribe to some of the item's events.
					this.drawables.Insert(index, drawable);
					drawable.DrawOrderChanged += new EventHandler(this.OnComponentDrawOrderChanged);
				}
			}

		}

		protected virtual void OnComponentRemoved(object sender, GameComponentCollectionEventArgs<T> e)
		{
			// Remove the item from the initialization pending list.
			if (!this.initialized)
			{
				this.notYetInitialized.Remove(e.Item);
			}

			// Determine if the item is updateable.
			IUpdateable updateable = e.Item as IUpdateable;
			if (updateable != null)
			{
				// Remove the item from the ordered list and unsubscribe from it's events.
				this.updateables.Remove(updateable);
				updateable.UpdateOrderChanged -= new EventHandler(this.OnComponentUpdateOrderChanged);
			}

			// Determine if the item is drawable.
			IDrawable drawable = e.Item as IDrawable;
			if (drawable != null)
			{
				// Remove the item from the ordered list and unsubscribe from it's events.
				this.drawables.Remove(drawable);
				drawable.DrawOrderChanged -= new EventHandler(this.OnComponentDrawOrderChanged);
			}
		}

		private void OnComponentUpdateOrderChanged(object sender, EventArgs e)
		{
			// Remove the item from the ordered list.
			IUpdateable updateable = sender as IUpdateable;
			this.updateables.Remove(updateable);

			// Determine where the item should be inserted.
			int index = this.updateables.BinarySearch(updateable, UpdateOrderComparer.Default);
			if (index < 0)
			{
				index = ~index;
				while (index < this.updateables.Count && this.updateables[index].UpdateOrder == updateable.UpdateOrder)
				{
					index++;
				}

				this.updateables.Insert(index, updateable);
			}
		}

		private void OnComponentDrawOrderChanged(object sender, EventArgs e)
		{
			// Remove the item from the ordered list.
			IDrawable drawable = sender as IDrawable;
			this.drawables.Remove(drawable);

			// Determine where the item should be inserted.
			int index = this.drawables.BinarySearch(drawable, DrawOrderComparer.Default);
			if (index < 0)
			{
				index = ~index;
				while (index < this.drawables.Count && this.drawables[index].DrawOrder == drawable.DrawOrder)
				{
					index++;
				}

				this.drawables.Insert(index, drawable);
			}
		}
	}
}
